//  This file is part of Swordbible - http://www.swordwarrior.net
//  Copyright (C) 2003-2008 Manu Mital mital.manu@gmail.com

//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2, or (at your option)
//  any later version.

//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.

//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software Foundation,
//  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// Search.cpp : implementation file
//

#include "stdafx.h"
#include "SwordBible.h"
#include "Search.h"
#include "SwordBibleDoc.h"
#include "SwordBibleView.h"
#include ".\search.h"
#include "MainFrm.h"
#include <regex.h> 




int CSearch::count = 0;
int  CSearch::cms_currentProgress = 0;
BOOL CSearch::isSearching = FALSE;
CString CSearch::toSearch = "";
sword::ListKey CSearch::searchResults = NULL; 
sword::VerseKey CSearch::scope;
int CSearch::flag;
int CSearch::searchType;



// CSearch dialog

IMPLEMENT_DYNAMIC(CSearch, CDialog)
CSearch::CSearch(CWnd* pParent)
	: CDialog(CSearch::IDD, pParent)

	
	, m_radioEntire(FALSE)
{ 
	

	count++;
	m_IscaseSensitive = FALSE;
	m_IsMatchWholeWord = TRUE;
	m_searchText = _T("");
	pModule = NULL;
	parent = pParent;
	
}

CSearch::~CSearch()
{
}

void CSearch::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST_SEARCHRESULTS, m_resultListControl);
	DDX_Control(pDX, IDC_COMBO_SEARCHSTART, m_searchStartControl);
	DDX_Control(pDX, IDC_COMBO_SEARCHEND, m_searchEndControl);
	DDX_Text(pDX, IDC_EDIT_FINDBOX, m_searchText);
	DDV_MaxChars(pDX, m_searchText, 50);
	DDX_Check(pDX, IDC_CHECK_MATCHCASE, m_IscaseSensitive);
	DDX_Check(pDX, IDC_CHECK_MATCHWHOLE, m_IsMatchWholeWord);

	DDX_Radio(pDX, IDC_RADIO_ENTIRE, m_radioEntire);
	DDX_Control(pDX, IDC_RICHEDIT_SEARCH_PREVIEW, m_resultPreview);
}


BEGIN_MESSAGE_MAP(CSearch, CDialog)
	ON_BN_CLICKED(ID_BUTTON_SEARCH, OnButtonSearch)


	ON_NOTIFY(NM_DBLCLK, IDC_LIST_SEARCHRESULTS, OnNMDblclkListSearchresults)
	ON_NOTIFY(NM_CLICK, IDC_LIST_SEARCHRESULTS, OnClickSearchResults)
	ON_BN_CLICKED(IDC_SEARCHRESULTS_VL, &CSearch::OnAddtoVerseList)
END_MESSAGE_MAP()


// CSearch message handlers

void CSearch::OnButtonSearch()
{
	// Upodate data before getting the options
	UpdateData(TRUE);


    // return if the user has specified nothing to search for
	if(m_searchText == "" || pModule == NULL) return;

	searchTerms.RemoveAll();	
	toSearch = "";
	CString temp = "";
	int i = 0;
	while(AfxExtractSubString(temp, m_searchText, i++  , ' '))
	{
		temp.Remove(' ');

		if(temp.GetLength())
		{
			toSearch = toSearch + " " + temp;
			searchTerms.Add(temp);
		}
	}

	
	m_resultListControl.DeleteAllItems();
	m_resultPreview.SetRTF((CString) "");

	// set the scope of the search
	SetSearchScope();

	// case sensitive search?
	if (m_IscaseSensitive == TRUE)	flag = 0;
	else flag = REG_ICASE;

	// phrase (-1) or multiword (-2)
   if (m_IsMatchWholeWord == TRUE)   searchType = -1;
   else searchType = -2;

   searchResults.ClearList();


	// call the search thread
	AfxBeginThread(Searcher,(LPVOID) pModule);

		
	// call the display thread
    ExecuteFunctionWithProgressDialog(StatusDisplay,
		_T("Searching"),
		NULL, NULL, _T("Ok to cancel calculation"), 
		THREAD_PRIORITY_NORMAL, this);

		
	for (searchResults = sword::BOTTOM; !searchResults.Error(); searchResults--) {
			//	Return Value : The index of the new item if successful or -1 otherwise.
			pModule->SetKey(searchResults);
			int Index = m_resultListControl.InsertItem(LVIF_TEXT,0, searchResults  , 0, 0, 0, NULL);
	}
	

	if(searchResults.Count()> 0)
	{
	m_resultListControl.SetItemState(0,LVIS_SELECTED, LVIS_SELECTED);

	//change the key
	sword::SWKey* key =  searchResults.getElement(0);
	pModule->SetKey(*key);


	// display the text
	CString display = font.GetHeaderRTF() +   "\\cf1\\b "+ (CString) key->getShortText() + " \\b0\\cf2 ";
	
	CString renderText = (CString) pModule->RenderText();
	
	renderText.Replace("\\par"," ");
	renderText.Replace("\\cf6","\\cf9");
	
	display = display + renderText + "\\par\\par " +  "}";

	// footnote tags are enclosed between href tags in bibles. Footnotes use color cf5 in the color table 
	// x reference tags are enclosed between href tags in bibles. cross references use color cf6 in the color table 
	display.Replace("<a href=\"\">*n",  "\\cf5\\b *n");
	display.Replace("<a href=\"\">*x",  "\\cf6\\b *x");
	display.Replace("</a>", "\\cf2\\b0 ");
	display.Replace("<a href=\"\">",  "\\cf6\\b ");



	m_resultPreview.SetRTF(display);
	
		// highlight the search word in results
	HighLight();

	}
		

	CString count;
	count.Format("%d results found. Double click search results to jump to a verse",searchResults.Count());
	GetDlgItem(IDC_STATIC_COUNT)->SetWindowText(count);



}

UINT CSearch::Searcher(LPVOID dummy)
{

	isSearching = TRUE;

	sword::SWModule *p = (sword::SWModule *) dummy;

	searchResults.ClearList();

	searchResults = p->Search(toSearch,searchType,flag ,&scope, 0, &percentUpdate);
		
	isSearching = FALSE;

	return 0;
}
	
ULONG CSearch::StatusDisplay(void* pData, CProgressThreadDlg* pProgressDlg)
{
   
    if (!pProgressDlg) return 0;

	CString pc;
    
	while (isSearching == TRUE)
	 {
		 pc.Format("%d ", CSearch::cms_currentProgress);
		 pProgressDlg->SetPercentageDone(CSearch::cms_currentProgress);
	 }


	  
  return 0;
}


void CSearch:: percentUpdate(char percent, void* userData)  {
	

	CSearch::cms_currentProgress = (int)percent;
	
	
	}


BOOL CSearch::OnInitDialog()
{
	CDialog::OnInitDialog();

	// set search dialog title
	if(pModule)
		SetWindowText("Searching: " + (CString) pModule->Description());



		// fill in the combo boxes that let user select a custom range	
	for(int i = 0; i < 66; i++) {
		m_searchStartControl.AddString(CSwordBibleDoc::bookCode[i][0]);
		m_searchEndControl.AddString(CSwordBibleDoc::bookCode[i][0]);
}

    // set the search start to Genesis
	m_searchStartControl.SetCurSel(0);

	// set the search end to Revelation
	m_searchEndControl.SetCurSel(66- 1);


	
	
   // Add titles for search results list box
	m_resultListControl.InsertColumn(0,"Verse" );
	

	
	// set the width of the search results list box
	CRect listRect;
	m_resultListControl.GetWindowRect(listRect);
	int width = listRect.Width();
	m_resultListControl.SetColumnWidth(0, width);


	
	m_resultListControl.EnableScrollBar(SB_HORZ,ESB_DISABLE_BOTH);

     m_resultListControl.SetExtendedStyle(m_resultListControl.GetExtendedStyle()
		 | LVS_EX_FULLROWSELECT);



	 searchResults.ClearList(); // clear the list containing search results

	

	return TRUE;  // return TRUE unless you set the focus to a control
	// EXCEPTION: OCX Property Pages should return FALSE

	
}

void CSearch::SetSearchScope()

{
	UpdateData();

	scope.ClearBounds();

	 int sel = GetCheckedRadioButton(IDC_RADIO_ENTIRE,IDC_RADIO_CUSTOM);

	if(sel == IDC_RADIO_ENTIRE)
	{ scope.LowerBound("Genesis"); scope.UpperBound("Revelation"); }

		else if(sel == IDC_RADIO_NT )
	{ scope.LowerBound("Matthew"); scope.UpperBound("Revelation"); }

		else if(sel == IDC_RADIO_OT )
	{ scope.LowerBound("Genesis"); scope.UpperBound("Malachi"); }

		else if(sel == IDC_RADIO_CUSTOM)
	{ 
		int startSel = m_searchStartControl.GetCurSel();
		int endSel = m_searchEndControl.GetCurSel();

		scope.LowerBound( CSwordBibleDoc::bookCode[startSel][0] );
		scope.UpperBound( CSwordBibleDoc::bookCode[endSel][0]);
	}
	
}




void CSearch::OnNMDblclkListSearchresults(NMHDR *pNMHDR, LRESULT *pResult)
{
	// TODO: Add your control notification handler code here
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

	if (searchResults.Count() < 1)
	{ *pResult = 0; return; }

	int item = pNMListView->iItem; // item is the zero based index

	
	
	sword::SWKey* key =  searchResults.getElement(item);

	if(key->Error()) return;



	if (!strcmp(pModule->Type(), "Biblical Texts")) {
	CSwordBibleDoc::key = *key;

	// Change the display in bible, notes, commentaries
	CWnd * pWnd = AfxGetMainWnd();

	CBiblePage::ActiveMainFrameCommand = "ShowChapter";
	pWnd->SendMessageToDescendants(ID_BIBLEPAGE_COMMAND, 0,0);

	if(CSwordBibleDoc::notesSynch)
	{
	CNotesPage::ActiveMainFrameCommand = "BibleSynch";
	pWnd->SendMessageToDescendants(ID_NOTES_COMMAND,0,0);
	}

	if(CSwordBibleDoc::bottomSynch)
	{
	CBottomPage::ActiveMainFrameCommand = "BibleSynch";
	pWnd->SendMessageToDescendants(ID_COMMENTARY_COMMAND,0,0);
	}  
	

	*pResult = 0;

	}

	else if (!strcmp(pModule->Type(), "Commentaries"))
	{
			CSwordBibleDoc::curBottomKey = *key;
		
		

			if(parent->GetSafeHwnd())
			{ 
				parent->SendMessage(WM_BOTTOM_NOTIFY,  CSwordBibleDoc::bottomBook() - 1,
				CSwordBibleDoc::bottomChapter() - 1 );
			}
			

	}
			

	
}



void CSearch::OnClickSearchResults(NMHDR *pNMHDR, LRESULT *pResult)
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

	if (searchResults.Count() < 1)
	{ *pResult = 0; return; }

	int item = pNMListView->iItem; // item is the zero based index
	
	sword::SWKey* key =  searchResults.getElement(item);

	if(key->Error()) return;

	//change the key
	pModule->SetKey(*key);

	// display the text
	CString display = font.GetHeaderRTF() +   "\\cf1\\b "+ (CString) key->getShortText() + " \\b0\\cf2 ";
	
	CString renderText = (CString) pModule->RenderText();
	
	renderText.Replace("\\par"," ");
	renderText.Replace("\\cf6","\\cf9");
	
	display = display + renderText + "\\par\\par " +  "}";

	// footnote tags are enclosed between href tags in bibles. Footnotes use color cf5 in the color table 
	// x reference tags are enclosed between href tags in bibles. cross references use color cf6 in the color table 
	display.Replace("<a href=\"\">*n",  "\\cf5\\b *n");
	display.Replace("<a href=\"\">*x",  "\\cf6\\b *x");
	display.Replace("</a>", "\\cf2\\b0 ");
	display.Replace("<a href=\"\">",  "\\cf6\\b ");



	m_resultPreview.SetRTF(display);

		// highlight the search word in results
	HighLight();

	*pResult = 0;
}

void CSearch::OnAddtoVerseList()
{

	if(searchResults.Count() < 1) // no search results
	{
		AfxMessageBox("There are no search results to save to verse list");
	    return;
	}
	

	// create the verse list window if it does not exist
	if (!CMainFrame::m_pModelessVL)
       CMainFrame::m_pModelessVL= new CVerseLists;

   if (!::IsWindow(CMainFrame::m_pModelessVL->GetSafeHwnd()))
	   CMainFrame::m_pModelessVL->Create(IDD_VERSELISTS, (CMainFrame*) AfxGetApp()->GetMainWnd() );


   CMainFrame::m_pModelessVL->SaveSearchResults();



}


void CSearch::HighLight()
{
	
   if(searchType == -1) //match whole word
	   HighLightWord(toSearch);

   else
   {	
	int terms = searchTerms.GetCount();
	for(int i = 0; i < terms; i++) HighLightWord(searchTerms.GetAt(i));
   }

	m_resultPreview.SetSel(-1,0); // deselect all 


}

void CSearch::HighLightWord(CString word)
{
	FINDTEXTEX ft1;      // FINDTEXTEX structure that would be used for searching for search terms in rich control
	ft1.chrg.cpMax = -1;
	ft1.chrg.cpMin = 0;

	m_resultPreview.SetSel(0,0); 
	
	BOOL stop = FALSE;
	ft1.lpstrText = word ;
	int count = word.GetLength();

	while(stop == FALSE)
	{
	
	     int foundAt = m_resultPreview.FindText(FR_DOWN , &ft1); 	
	
	    if(foundAt != -1) {	                                // select & highlight the search word
		m_resultPreview.SetSel( foundAt, foundAt + count );
		m_resultPreview.SetSelColor(RGB(255,0,0));
	
		m_resultPreview.SetSel( foundAt, foundAt + count );
		m_resultPreview.SetSelectionBold();

		ft1.chrg.cpMin = foundAt + 1;	
	     } 

	    else stop = TRUE;

	}

}
